import ast  # 导入抽象语法树模块，用于解析 Python 代码
import os.path  # 导入 os.path 模块，用于处理文件路径
import pandas as pd  # 导入 pandas 模块，用于数据处理和 Excel 文件生成


# 递归搜索指定目录下所有的 py 文件，并保存到列表中返回
def search_py_files(folder):
    """
    递归搜索指定目录下的所有 .py 文件，并将文件路径保存到列表中返回。
    
    Args:
        folder (str): 要搜索的目录路径。
    
    Returns:
        list: 包含所有 .py 文件路径的列表。
    """
    py_files = []  # 初始化一个空列表，用于存储 .py 文件路径
    for file in os.listdir(folder):  # 遍历指定目录下的所有文件和子目录
        file_path = os.path.join(folder, file)  # 构造完整的文件路径
        if os.path.isdir(file_path):  # 如果是目录，则递归搜索子目录
            py_files.extend(search_py_files(file_path))  # 将子目录中的 .py 文件路径添加到列表中
        elif file.endswith('.py'):  # 如果是 .py 文件，则添加到列表中
            py_files.append(file_path)
    return py_files  # 返回包含所有 .py 文件路径的列表


def read_class(src_folder, desc_folder=""):
    """
    读取指定目录下的所有 .py 文件，提取代码结构并生成 Excel 文件。
    
    Args:
        src_folder (str): 源目录路径，包含要分析的 .py 文件。
        desc_folder (str, optional): 输出 Excel 文件的目录路径，默认为空字符串。
    
    Returns:
        None
    """
    # 搜索源目录下的所有 .py 文件
    py_files = search_py_files(src_folder)  # 调用 search_py_files 函数获取所有 .py 文件路径
    all_data = []  # 初始化一个空列表，用于存储所有代码结构数据

    for file_path in py_files:  # 遍历每个 .py 文件路径
        # 获取当前文件的目录路径
        abs_folder = os.path.dirname(file_path)
        print("abs_folder", abs_folder)
        # 获取目录名称
        folder_name = os.path.basename(abs_folder)
        print("folder_name", folder_name)
        # 提取单个文件的代码结构
        data = extract_code_structure(file_path)  # 调用 extract_code_structure 函数提取代码结构
        print("data", data)  # 打印提取的代码结构数据，便于调试

        # 展平数据结构
        module_name = data['module']  # 获取模块名
        for class_name, methods in data['classes'].items():  # 遍历类和方法
            # 遍历类和方法，将每个方法单独写入一行
            for method in methods:
                all_data.append({
                    # 要绝对路径就用abs_folder，要文件名就用folder_name
                    "folder_name": folder_name,  # 文件所在目录
                    'module': module_name,  # 模块名
                    'class_name': class_name,  # 类名
                    'method': method  # 方法名
                })

    # 根据目标目录生成 Excel 文件
    if desc_folder == "":  # 如果未指定目标目录，则使用源目录
        generate_excel(src_folder, all_data)  # 调用 generate_excel 函数生成 Excel 文件
    else:
        generate_excel(desc_folder, all_data)  # 调用 generate_excel 函数生成 Excel 文件


# 生成 excel
def generate_excel(folder, all_data):
    """
    将数据保存为 Excel 文件。
    
    Args:
        folder (str): 输出文件的目录路径。
        all_data (list): 包含代码结构数据的列表。
    
    Returns:
        None
    """
    # 创建 DataFrame
    df = pd.DataFrame(all_data)  # 将数据转换为 pandas 的 DataFrame 格式

    # 将 DataFrame 保存到 Excel 文件
    output_file = os.path.join(folder, 'code_structure.xlsx')  # 构造输出文件路径
    df.to_excel(output_file, index=False)  # 将 DataFrame 保存为 Excel 文件，不包含索引列
    print(f"保存到 {output_file} 成功")  # 打印成功信息


def extract_code_structure(file_path):
    """
    提取 Python 文件中的模块名、导入模块、类名、方法名和独立函数名。
    
    Args:
        file_path (str): .py 文件路径。
    
    Returns:
        dict: 结构为 {
            'module': 当前模块名,
            'imports': 导入的模块列表,
            'classes': {类名: 方法列表},
            'functions': 独立函数列表
        }
    """
    with open(file_path, 'r', encoding='utf-8') as f:  # 打开 .py 文件，读取代码内容
        code = f.read()  # 读取文件内容

    tree = ast.parse(code)  # 使用 ast 模块解析代码，生成抽象语法树
    result = {
        'module': os.path.splitext(os.path.basename(file_path))[0],  # 从文件名提取模块名
        'imports': [],  # 初始化导入模块列表
        'classes': {},  # 初始化类及其方法的字典
        'functions': []  # 初始化独立函数列表
    }

    # 遍历 AST 节点
    for node in ast.walk(tree):  # 遍历抽象语法树中的所有节点
        # 提取导入的模块
        if isinstance(node, (ast.Import, ast.ImportFrom)):  # 如果是导入节点
            for alias in node.names:  # 遍历导入的模块
                if isinstance(node, ast.ImportFrom):  # 如果是 from ... import ... 形式的导入
                    module_name = node.module  # 获取模块名
                    if node.level > 0:  # 如果是相对导入
                        module_name = f"{'.' * node.level}{module_name or ''}"  # 构造相对导入路径
                else:  # 如果是普通导入
                    module_name = alias.name  # 获取模块名
                result['imports'].append(module_name)  # 将模块名添加到导入列表中

        # 提取独立函数（非类方法）
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):  # 如果是函数定义节点
            result['functions'].append(node.name)  # 将函数名添加到独立函数列表中

        # 提取类及其方法
        if isinstance(node, ast.ClassDef):  # 如果是类定义节点
            class_name = node.name  # 获取类名
            methods = []  # 初始化方法列表
            for child in node.body:  # 遍历类体中的所有节点
                if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):  # 如果是方法定义节点
                    methods.append(child.name)  # 将方法名添加到方法列表中
            result['classes'][class_name] = methods  # 将类名和方法列表添加到字典中

    # 去重导入模块名
    result['imports'] = list(set(result['imports']))  # 去重导入模块列表
    return result  # 返回提取的代码结构数据


if __name__ == '__main__':
    # 程序入口
    print("程序开始执行了")  # 打印程序启动信息
    fd = os.path.join(os.path.dirname(__file__), '../myclass')  # 构造源目录路径
    read_class(fd)  # 调用 read_class 函数，开始分析代码并生成 Excel 文件
